using System;
using System.Collections.Generic;
using System.Linq;

namespace Think.Helpers
{
    public static class CollectionHelpers
    {
        /// <summary>
        /// Determine if the given index () is within the range of "this" list (<paramref name="a_this"/>).
        /// </summary>
        /// <typeparam name="TItem">Type of item.</typeparam>
        /// <param name="a_this">"This" list.</param>
        /// <param name="a_index"></param>
        /// <returns></returns>
        public static bool InRange<TItem>(this IList<TItem> a_this, int a_index)
        {
            #region Argument Validation

            if (a_this == null)
                throw new NullReferenceException("Object reference not set to an instance of an object.");

            #endregion

            return 0 <= a_index && a_index < a_this.Count;
        }

        /// <summary>
        /// Push the given item (<paramref name="a_item"/>) to the first of "this" list 
        ///     (<paramref name="a_this"/>).
        /// </summary>
        /// <typeparam name="TItem">Type of item.</typeparam>
        /// <param name="a_this">"This" list.</param>
        /// <param name="a_item">Item to push.</param>
        /// <exception cref="NullReferenceException">Thrown if "<paramref name="a_this"/>" is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_item"/>" is null.</exception>
        public static void PushFirst<TItem>(this IList<TItem> a_this, TItem a_item)
        {
            #region Argument Validation

            if (a_this == null)
                throw new NullReferenceException("Object reference not set to an instance of an object.");

            if (a_item == null)
                throw new ArgumentNullException("a_item");

            #endregion

            if (a_this.Any())
                a_this.Insert(0, a_item);
            else
                a_this.Add(a_item);
        }

        /// <summary>
        /// Push the given item (<paramref name="a_item"/>) to the last of "this" list 
        ///     (<paramref name="a_this"/>).
        /// </summary>
        /// <typeparam name="TItem">Type of item.</typeparam>
        /// <param name="a_this">"This" list.</param>
        /// <param name="a_item">Item to push.</param>
        /// <exception cref="NullReferenceException">Thrown if "<paramref name="a_this"/>" is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_item"/>" is null.</exception>
        public static void PushLast<TItem>(this IList<TItem> a_this, TItem a_item)
        {
            #region Argument Validation

            if (a_this == null)
                throw new NullReferenceException("Object reference not set to an instance of an object.");

            if (a_item == null)
                throw new ArgumentNullException("a_item");

            #endregion
            
            a_this.Add(a_item);
        }


        /// <summary>
        /// Pop the first item from "this" list (<paramref name="a_this"/>).
        /// </summary>
        /// <typeparam name="TItem">Type of item.</typeparam>
        /// <param name="a_this">"This" list.</param>
        /// <returns>Popped item.</returns>
        /// <exception cref="NullReferenceException">Thrown if "<paramref name="a_this"/>" is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown if "<paramref name="a_this"/>" list is empty.</exception>
        public static TItem PopFirst<TItem>(this IList<TItem> a_this)
        {
            #region Argument Validation

            if (a_this == null)
                throw new NullReferenceException("Object reference not set to an instance of an object.");

            #endregion
            
            if (!a_this.Any())
                throw new InvalidOperationException("Collection is empty!");

            var element = a_this.First();
            a_this.RemoveAt(0);

            return element;
        }

        /// <summary>
        /// Pop the given item from "this" list (<paramref name="a_this"/>).
        /// </summary>
        /// <typeparam name="TItem">Type of item.</typeparam>
        /// <param name="a_this">"This" list.</param>
        /// <returns>Popped item.</returns>
        /// <exception cref="NullReferenceException">Thrown if "<paramref name="a_this"/>" is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown if "<paramref name="a_this"/>" list is empty.</exception>
        public static TItem PopLast<TItem>(this IList<TItem> a_this)
        {
            #region Argument Validation

            if (a_this == null)
                throw new NullReferenceException("Object reference not set to an instance of an object.");

            #endregion
            
            if (!a_this.Any())
                throw new InvalidOperationException("Collection is empty!");

            var element = a_this.Last();
            a_this.RemoveAt(a_this.Count - 1);

            return element;
        }

        /// <summary>
        /// Split this list at the given index (<paramref name="a_this"/>).
        /// </summary>
        /// <typeparam name="TItem">Type of item.</typeparam>
        /// <param name="a_this">"This" list.</param>
        /// <param name="a_index">Split index.</param>
        /// <returns>Latter segment of split.</returns>
        /// <exception cref="NullReferenceException">Thrown if "<paramref name="a_this"/>" is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if "<paramref name="a_index"/> is out of range of "<paramref name="a_this"/>".</exception>
        public static List<TItem> Split<TItem>(this IList<TItem> a_this, int a_index)
        {
            #region Argument Validation

            if (a_this == null)
                throw new NullReferenceException("Object reference not set to an instance of an object.");

            #endregion

            if (!InRange(a_this, a_index))
                throw new ArgumentOutOfRangeException("a_index");

            var splitOff = new List<TItem>();
            var count = a_this.Count;
            for (int i = a_index; i < count; i++)
            {
                var item = a_this.PopLast();
                splitOff.PushFirst(item);
            }

            return splitOff;
        }
    }
}